#ifndef _HASHMAP_C
#define _HASHMAP_C

#include "_HashMap.h"

/* Constructor */
void
initHashMap(HashMap *initThis,
            size_t   keySize,
            size_t (*hashKey)   (const void  *key, 
                                 const size_t keySize),
            int    (*compareKey)(const void *keyOne, 
                                 const void *keyTwo))
{
    size_t i;

    if(hashKey == NULL) {
        initThis->hash = &__genericHash;
    } else {
        initThis->hash = hashKey;
    }

    if(compareKey == NULL) {
        initThis->compare = &__genericCompare;
    } else {
        initThis->compare = compareKey;
    }

    initVector(&(initThis->buckets));

    initThis->keySize = keySize;
    initThis->length = 0;

    /* 
     * Setting all the buckets to NULL because otherwise they are just the
     * garbage that malloc returned. We use the functionality of empty buckets
     * being NULL later, so don't remove this.
     */
    for(i = 0; i < initThis->buckets.capacity; i++) {
        VectorAdd(&initThis->buckets, NULL);
    }
}

/* Destructor */
/* 
 * Returns an Array of the keys and values in h.
 * Elements go key, value, key, value, etc.
 */
Array 
deleteHashMap(HashMap *deleteThis)
{
    Array toReturn;

    if(deleteThis != NULL) {
        toReturn = _deleteHashMapIntoKeysAndValues(deleteThis);
        deleteVector(&(deleteThis->buckets));
    } else {
        initArray(&toReturn, 0);
    }

    return toReturn;
}

/* Given a HashMap, this function will return the i
address of the value that the key maps to. 
toAdd is if you want to append to the bucket if you don't find the key in it.*/
void **
_getValueAddress(HashMap *h, void *key, int toAdd)
{
    size_t i;
    float currentLoadFactor;
    Vector *bucket = NULL;
    void *aKey = NULL;

    bucket = _getBucket(h, key);

    /* Maybe ++i, ++i is faster? */
    for(i = 0; i < bucket->length; i += 2) {
        aKey = VectorGet(bucket, i);

        if(h->compare(aKey, key) == 0) {
            return &(bucket->data[i + 1]);
        }
    }

    if(toAdd) {

        /* Checking if resizing is needed */
        currentLoadFactor = (float)(h->length)/(float)(h->buckets.length);
        if(currentLoadFactor > _HASHMAP_MAX_LOAD_FACTOR) {
            _HashMapResize(h);
            return _getValueAddress(h, key, toAdd);
        }

        VectorAdd(bucket, key);
        VectorAdd(bucket, NULL);
        h->length++;
        /* Returning address of the last slot in the bucket */
        return &(bucket->data[bucket->length - 1]);
    }

    return NULL;
}

Vector *
_getBucket(HashMap *h, void *key)
{
    size_t keyIndex;
    Vector *bucket = NULL;

    if(h != NULL) {
        keyIndex = h->hash(key, h->keySize) % h->buckets.length;
        bucket = VectorGet(&(h->buckets), keyIndex);

        /* If the bucket doesn't exist, create it and store it in h->buckets */
        if(bucket == NULL) {
            bucket = malloc(sizeof(Vector));
            initVector(bucket);
            VectorSet(&(h->buckets), keyIndex, bucket);
        }
    }

    return bucket;
}

void *
HashMapGet(HashMap *h, void *key)
{
    void **valueAddress = NULL;

    if(h != NULL) {
        valueAddress = _getValueAddress(h, key, 0);
        if(valueAddress != NULL) {
            return *valueAddress;
        }
    }

    return NULL;
}

Pair
HashMapSet(HashMap *h, void *key, void *value)
{
    Pair oldKeyValuePair;
    void **valueAddress = NULL;
    void **keyAddress = NULL;

    if(h != NULL) {
        valueAddress = _getValueAddress(h, key, 1);
        /* This is hacky, clean it up */
        --valueAddress;
        keyAddress = valueAddress;
        ++valueAddress;

        initPair(&oldKeyValuePair, *(keyAddress), *(valueAddress));
        *(valueAddress) = value;
        *(keyAddress) = key;
    } else {
        initPair(&oldKeyValuePair, NULL, NULL);
    }

    return oldKeyValuePair;
}

/* TODO: Needs HashMapShrink code */
Pair
HashMapExtract(HashMap *h, void *key)
{
    Pair toReturn;
    void **valueAddress = NULL;
    void **keyAddress = NULL;
    size_t keyIndex;
    Vector *bucket = NULL;

    if(h != NULL) {
        bucket = _getBucket(h, key);
        valueAddress = _getValueAddress(h, key, 0);

        if(valueAddress != NULL) {
           --valueAddress;
           keyAddress = valueAddress;
           ++valueAddress;
           initPair(&toReturn, *keyAddress, *valueAddress);

           keyIndex = (size_t)(keyAddress - &(bucket->data[0]));
           VectorExtract(bucket, keyIndex);
           VectorExtract(bucket, keyIndex);

           *keyAddress = NULL;
           *valueAddress = NULL;

           h->length--;

           return toReturn; 
        }
    }

    initPair(&toReturn, NULL, NULL);
    return toReturn;
}

int
HashMapContainsKey(HashMap *h, void *key)
{
    void **valueAddress = NULL;

    if(h != NULL) {
        valueAddress = _getValueAddress(h, key, 0);
        if(valueAddress != NULL) {
            return 1;
        }
    }

    return 0;
}

Array
_deleteHashMapIntoKeysAndValues(HashMap *h) 
{
    size_t i, j = 0;
    Array toReturn;
    Vector *bucket = NULL;

    if(h != NULL) {
        initArray(&toReturn, 2 * (h->length));
        
        /* While there are buckets we haven't deleted */
        while(h->buckets.length != 0) {
            bucket = VectorExtract(&(h->buckets), 0);
            if(bucket != NULL) {
                /* 
                 * Copy all the keys and values from the bucket
                 * into toReturn.
                 */
                for(i = 0; i < bucket->length; i++, j++) {
                    ArraySet(&toReturn, j, VectorGet(bucket, i));
                }

                deleteVector(bucket);
                free(bucket);
            }
        }
    } else {
        initArray(&toReturn, 0);
    }

    return toReturn;
}


void
_HashMapResize(HashMap *h)
{
    size_t i;
    Array hValues;
    void *key = NULL;
    void *value = NULL;

    if(h != NULL) {

        /*
         * Extracting all the keys and values from h. This is because once we 
         * resize h->buckets, the bucket that each key gets hashed into will
         * change.
         */
        hValues = _deleteHashMapIntoKeysAndValues(h);

        /* Expanding buckets */
        while((float)(h->length)/(float)(h->buckets.length) > 
            _HASHMAP_MIN_LOAD_FACTOR)
        {
            printf("expanding\n");
            VectorAdd(&(h->buckets), NULL);
        }
  
        /* Adding everything back into h */ 
        for(i = 0; i < hValues.length; i += 2) {
            key = ArrayGet(&hValues, i);
            value = ArrayGet(&hValues, i + 1);
            HashMapSet(h, key, value);
        }

        deleteArray(&hValues);
    }        
}

#endif
